﻿using MongoDB.Driver;
using MongoDB.Driver.Linq;
using Sustainalytics.ClientDW.Entities;
using Sustainalytics.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic;
using System.Linq.Expressions;
using Sustainalytics.BusinessResearchProductStates.Entities.ResearchState;
using Sustainalytics.MongoDB.Extensions;

namespace Sustainalytics.ClientDW.DataAccessLayer
{
    public class ClientDWGlobalCompactRepository : IClientDWGlobalCompactRepository
    {
        private static IMongoDatabase _database;
        private IMongoCollection<CorporateData> _corporateDataCollection;
        private static string _connectionString;
        private static string _databaseName;

        private ClientDWGlobalCompactRepository()
        {
        }

        public static IClientDWGlobalCompactRepository GetInstance(string connectionString)
        {
            if (string.IsNullOrEmpty(connectionString))
                throw new ArgumentException(
                    "A connection string must be provided to create an IClientDWGlobalCompactRepository instance!");

            _connectionString = connectionString;

            var repo = new ClientDWGlobalCompactRepository();
            repo.SetDatabase(connectionString);

            return repo;
        }

        public void SetDatabase(string connectionString)
        {
            var url = MongoUrl.Create(connectionString);

            if (string.IsNullOrWhiteSpace(url.DatabaseName))
            {
                throw new ArgumentException("No database specified in the connection string!");
            }

            var client = new MongoClient(url);

            _database = client.GetDatabase(url.DatabaseName);
            _databaseName = url.DatabaseName;
        }

        public void Cleanup()
        {
            _database.Client.DropDatabase(_databaseName);
        }


        private Guid HasGcResearch(Guid companyId)
        {
            var id = MongoFactory.GetCollectionFromDatabase<CorporateData>(_connectionString)
                .Find(cd => cd.Id == companyId
                        && cd.ResearchParentId == null
                        && cd.GccsResearchState.ResearchState == ResearchState.ResearchAvailable
                        && cd.GccsResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                .Project(cd => cd.Id)
                .SingleOrDefault();

            return id;
        }

        private List<Guid> HasGcResearch()
        {
            return MongoFactory.GetCollectionFromDatabase<CorporateData>(_connectionString)
                .Find(cd => cd.ResearchParentId == null
                         && cd.GccsResearchState.ResearchState == ResearchState.ResearchAvailable
                         && cd.GccsResearchState.ClientFacingProducts.Contains(ClientFacingProduct.GA))
                .Project(cd => cd.Id)
                .ToList();
        }


        #region CurrentQuarter

        public GCCurrentQuarter GetCurrentQuarter()
        {
            return _database.GetCollection<GCCurrentQuarter>(typeof(GCCurrentQuarter).Name).Find(_ => true).FirstOrDefault();
        }

        public void SaveCurrentQuarter(GCCurrentQuarter document)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCCurrentQuarter>(_connectionString);

            collection.Database.DropCollection(typeof(GCCurrentQuarter).Name);

            collection.InsertOne(document);
        }

        #endregion

        #region GCOverallCompanySummaryExtended

        public int GetGCOverallCompanySummaryExtendedCount(GCOverallCompanySummaryExtendedFilters filters, List<int> universeIds, GCAssessment? overallAssessment)
        {
            var companyIds = HasGcResearch();

            var agg = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummaryExtended>(_connectionString)
                .Aggregate()
                .Match(x => companyIds.Contains(x.Id));

            agg = GetFilteredDocuments(filters, universeIds, overallAssessment, agg);
            var countValue = agg.Count().FirstOrDefault();

            return countValue != null ? (int)countValue.Count : 0;
        }

        public List<GCOverallCompanySummaryExtended> GetGCOverallCompanySummaryExtendedList(
            GCOverallCompanySummaryExtendedFilters filters,
            List<int> universeIds, GCAssessment? overallAssessment, int? skip, int? take, string[] orderBy = null, bool orderByAsc = true)
        {
            var companyIds = HasGcResearch();

            var agg = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummaryExtended>(_connectionString)
                .Aggregate()
                .Match(x => companyIds.Contains(x.Id));

            agg = GetFilteredDocuments(filters, universeIds, overallAssessment, agg);

            if (orderBy != null && orderBy.Length > 0)
            {
                var elemType = Expression.Parameter(typeof(GCOverallCompanySummaryExtended), "x");
                MemberExpression member = null;
                Expression<Func<GCOverallCompanySummaryExtended, object>> expression;
                string[] sortPath;
                foreach (var orderByItem in orderBy)
                {
                    if (orderByItem != "companyProfile.nameLowercase")
                    {
                        sortPath = orderByItem.Split(new char[] { '.' });
                        for (var i = 0; i < sortPath.Length; i++)
                        {
                            if (i == 0)
                                member = Expression.Property(elemType, typeof(GCOverallCompanySummaryExtended), char.ToUpper(sortPath[i].First()) + sortPath[i].Substring(1));
                            else
                                member = Expression.Property(member, char.ToUpper(sortPath[i].First()) + sortPath[i].Substring(1));
                        }
                        expression = Expression.Lambda<Func<GCOverallCompanySummaryExtended, object>>(Expression.Convert(member, typeof(object)), elemType);
                        agg = orderByAsc ? agg.SortBy(expression).ThenBy(x => x.CompanyProfile.NameLowercase) : agg.SortByDescending(expression).ThenBy(x => x.CompanyProfile.NameLowercase);
                    }
                }

                if (orderBy[0] == "companyProfile.nameLowercase")
                {
                    agg = orderByAsc ? agg.SortBy(x => x.CompanyProfile.NameLowercase) : agg.SortByDescending(x => x.CompanyProfile.NameLowercase);
                }
            }

            if (skip.HasValue)
            {
                agg = agg.Skip(skip.Value);
            }

            if (take.HasValue)
            {
                agg = agg.Limit(take.Value);
            }

            var result = agg.ToList();

            var companyIdsToMatch = result.Select(x => x.Id);

            var corporateData = MongoFactory.GetCollectionFromDatabase<CorporateData>(_connectionString)
                .Find(Builders<CorporateData>.Filter.In(cd => cd.Id, companyIdsToMatch))
                .Project<CorporateData>(Builders<CorporateData>.Projection.Include(x=> x.SubPeerGroup))
                .ToList()
                .ToDictionary(x=> x.Id, x=> x.SubPeerGroup);

            for (var i = 0; i < result.Count; i++)
            {
                corporateData.TryGetValue(result[i].Id, out var subPeergroup);
                result[i].CompanyProfile.Industry = subPeergroup;
            }

            return result;
        }


        private static IAggregateFluent<GCOverallCompanySummaryExtended> GetFilteredDocuments(
            GCOverallCompanySummaryExtendedFilters filters,
            List<int> universeIds,
            GCAssessment? overallAssessment,
            IAggregateFluent<GCOverallCompanySummaryExtended> agg)
        {
            if (filters != null && filters.OverallAssessmentFilter != null)
            {
                overallAssessment = filters.OverallAssessmentFilter;
            }

            agg = agg.Match(c =>
                 ((overallAssessment != null && c.OverallCompanyAssessment == overallAssessment) ||
                 (overallAssessment == null && c.OverallCompanyAssessment != GCAssessment.Compliant))
                  && c.UniverseIds.Any(u => universeIds.Contains(u)));

            if (filters != null)
            {
                if (!string.IsNullOrEmpty(filters.CompanyNameLowerCase))
                {
                    agg = agg.Match(x => x.CompanyProfile.NameLowercase.Contains(filters.CompanyNameLowerCase.ToLowerInvariant()));
                }

                if (!string.IsNullOrEmpty(filters.Industry))
                {
                    agg = agg.Match(x => x.CompanyProfile.Industry.ToLowerInvariant().Contains(filters.Industry.ToLowerInvariant()));
                }

                if (filters.PrinciplesNCStrFilter != null)
                {
                    var ncArr = filters.PrinciplesNCStrFilter.Split('|').Select(x => int.Parse(x));
                    Expression<Func<GCOverallCompanySummaryExtended, bool>> predicateNC = c => true;

                    //if we have other criteria but N/A
                    if (ncArr.Any(x => x != 0))
                    {
                        var ncArrFlt = ncArr.Where(x => x != 0).ToList();
                        predicateNC = predicateNC.And(x => x.NonCompliantPrinciples.Any(t => ncArrFlt.Contains(t.Id)));
                    }

                    //if we have  N/A
                    if (ncArr.Any(x => x == 0))
                    {
                        if (ncArr.Count() > 1)
                        {
                            predicateNC = predicateNC.Or(x => !x.NonCompliantPrinciples.Any());
                        }
                        else
                        {
                            predicateNC = predicateNC.And(x => !x.NonCompliantPrinciples.Any());
                        }
                    }

                    agg = agg.Match(predicateNC);
                }

                if (filters.PrinciplesWLStrFilter != null)
                {
                    var wlArr = filters.PrinciplesWLStrFilter.Split('|').Select(x => int.Parse(x));

                    Expression<Func<GCOverallCompanySummaryExtended, bool>> predicateWL = c => true;

                    if (wlArr.Any(x => x != 0))
                    {
                        var wlArrFlt = wlArr.Where(x => x != 0).ToList();
                        predicateWL = predicateWL.And(x => x.WatchListPrinciples.Any(t => wlArrFlt.Contains(t.Id)));
                    }

                    if (wlArr.Any(x => x == 0))
                    {
                        if (wlArr.Count() > 1)
                        {
                            predicateWL = predicateWL.Or(x => !x.WatchListPrinciples.Any());
                        }
                        else
                        {
                            predicateWL = predicateWL.And(x => !x.WatchListPrinciples.Any());
                        }
                    }

                    agg = agg.Match(predicateWL);
                }
            }

            return agg;
        }

        public void SaveGcOverallCompanyExtended(List<GCOverallCompanySummaryExtended> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummaryExtended>(_connectionString);

            collection.Database.DropCollection(typeof(GCOverallCompanySummaryExtended).Name);

            collection.InsertMany(documents);

            collection.Indexes.CreateOne(Builders<GCOverallCompanySummaryExtended>.IndexKeys.Ascending(x => x.OverallCompanyAssessment));
        }

        public void UpdateGCOverallCompanySummaryExtendedWithUniverseIds(Guid companyId, List<int> universeIds)
        {
            var collection = _database.GetCollection<GCOverallCompanySummaryExtended>(typeof(GCOverallCompanySummaryExtended).Name);

            var query = Builders<GCOverallCompanySummaryExtended>.Filter.Eq(x => x.Id, companyId);
            var update = Builders<GCOverallCompanySummaryExtended>.Update.Set(x => x.UniverseIds, universeIds);

            collection.FindOneAndUpdate(query, update);
        }

        #endregion

        #region GCOverallCompanySummary

        public GCOverallCompanySummary GetGCOverallCompanySummaryByCompany(List<int> universeIds, Guid companyId, GCAssessment? overallAssessment, bool intersect = true)
        {
            var id = HasGcResearch(companyId);

            var query = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummary>(_connectionString)
                .Find(c => c.Id == id)
                .ToEnumerable()
                .AsQueryable();

            query = (intersect)
                ? query.Where(c => c.CompanyProfile != null && c.CompanyProfile.Id == companyId
                                && c.UniverseIds.Any(u => universeIds.Contains(u)))
                : query.Where(c => c.CompanyProfile != null && c.CompanyProfile.Id == companyId);

            if (overallAssessment.HasValue)
            {
                query = query.Where(c => c.OverallCompanyAssessment == overallAssessment.Value);
            }

            return query.FirstOrDefault();
        }

        public void SaveGcOverallCompanySummary(List<GCOverallCompanySummary> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCOverallCompanySummary>(_connectionString);

            collection.Database.DropCollection(typeof(GCOverallCompanySummary).Name);

            collection.InsertMany(documents);

            collection.Indexes.CreateOneAsync(Builders<GCOverallCompanySummary>.IndexKeys.Ascending(x => x.OverallCompanyAssessment));
        }

        #endregion

        #region GCPrincipleConclusion

        public GCPrincipleConclusion GetGCPrincipleConclusionByPrincipleSummary(Guid principleSummaryId)
        {
            var col = _database.GetCollection<GCPrincipleConclusion>("GCPrincipleConclusion");

            var query = col.AsQueryable().Where(c => c.Id == principleSummaryId);

            return query.FirstOrDefault();
        }

        public void SaveGcPrincipleConclusion(List<GCPrincipleConclusion> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCPrincipleConclusion>(_connectionString);

            collection.Database.DropCollection(typeof(GCPrincipleConclusion).Name);

            collection.InsertMany(documents);
        }

        #endregion

        #region GCPrincipleEventIndicatorImpact

        public List<GCPrincipleEventIndicatorImpact> GetGCPrincipleEventIndicatorImpactList(Guid principleSummaryId)
        {
            var col = _database.GetCollection<GCPrincipleEventIndicatorImpact>("GCPrincipleEventIndicatorImpact");

            var query = col.AsQueryable().Where(c => c.PrincipleSummaryId == principleSummaryId);

            query = query.OrderBy(c => c.IssueType);

            return query.ToList();
        }

        public void SaveGCPrincipleEventIndicatorImpact(List<GCPrincipleEventIndicatorImpact> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCPrincipleEventIndicatorImpact>(_connectionString);

            collection.Database.DropCollection(typeof(GCPrincipleEventIndicatorImpact).Name);

            collection.InsertMany(documents);
        }

        #endregion

        #region GCSource

        public List<GCSource> GetGCSourceList(Guid companyId, int? type, GCAssessment? eventIndicatorAssessment)
        {
            var id = HasGcResearch(companyId);

            var query = MongoFactory.GetCollectionFromDatabase<GCSource>(_connectionString)
                .Find(c => c.CompanyId == id)
                .ToEnumerable()
                .AsQueryable();

            if (eventIndicatorAssessment.HasValue)
            {
                query = query.Where(c => c.EventIndicatorAssessment == eventIndicatorAssessment);
            }

            if (type.HasValue)
            {
                query = query.Where(c => c.Type == type.Value);
            }

            return query.ToList();
        }

        public void SaveGCSources(List<GCSource> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCSource>(_connectionString);

            collection.Database.DropCollection(typeof(GCSource).Name);

            collection.InsertMany(documents);
        }

        #endregion

        #region GCPrincipleEventIndicator

        public List<GCPrincipleEventIndicator> GetGCPrincipleEventIndicatorList(Guid principleSummaryId)
        {
            var col = _database.GetCollection<GCPrincipleEventIndicator>("GCPrincipleEventIndicator");

            var query = col.AsQueryable().Where(c => c.PrincipleSummaryId == principleSummaryId);

            query = query.OrderBy(c => c.IssueType);

            return query.ToList();
        }

        public void SaveGCPrincipleEventIndicator(List<GCPrincipleEventIndicator> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCPrincipleEventIndicator>(_connectionString);

            collection.Database.DropCollection(typeof(GCPrincipleEventIndicator).Name);

            collection.InsertMany(documents);
        }

        #endregion

        #region GCPrincipleEventIndicatorExtended

        public void SaveGCPrincipleEventIndicatorExtended(List<GCPrincipleEventIndicatorExtended> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCPrincipleEventIndicatorExtended>(_connectionString);

            collection.Database.DropCollection(typeof(GCPrincipleEventIndicatorExtended).Name);

            collection.InsertMany(documents);
        }

        public List<GCPrincipleEventIndicatorExtended> GetGCPrincipleEventIndicatorExtendedList(Guid companyId, GCAssessment? overallAssessment)
        {
            var id = HasGcResearch(companyId);

            var query = MongoFactory.GetCollectionFromDatabase<GCPrincipleEventIndicatorExtended>(_connectionString)
                .Find(c => c.CompanyId == id)
                .ToEnumerable()
                .AsQueryable();

            if (overallAssessment != null)
            {
                query = query.Where(c => c.EventIndicatorAssessment == overallAssessment.Value);
            }

            query = query.OrderBy(c => c.IssueType);

            return query.ToList();
        }

        #endregion

        #region GCPrincipleEventIndicatorManagement

        public List<GCPrincipleEventIndicatorManagement> GetGCPrincipleEventIndicatorManagementList(Guid principleSummaryId)
        {
            var col = _database.GetCollection<GCPrincipleEventIndicatorManagement>("GCPrincipleEventIndicatorManagement");

            var query = col.AsQueryable().Where(c => c.PrincipleSummaryId == principleSummaryId);

            query = query.OrderBy(c => c.IssueType);

            return query.ToList();
        }

        public void SaveGCPrincipleEventIndicatorManagement(List<GCPrincipleEventIndicatorManagement> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCPrincipleEventIndicatorManagement>(_connectionString);

            collection.Database.DropCollection(typeof(GCPrincipleEventIndicatorManagement).Name);

            collection.InsertMany(documents);
        }

        #endregion

        #region GCPrincipleSummary

        public List<GCPrincipleSummary> GetGCPrincipleSummaryList(Guid companyId, GCAssessment? overallAssessment)
        {
            var id = HasGcResearch(companyId);

            var query = MongoFactory.GetCollectionFromDatabase<GCPrincipleSummary>(_connectionString)
                .Find(c => c.CompanyId == id)
                .ToEnumerable()
                .AsQueryable();

            if (overallAssessment.HasValue)
            {
                query = query.Where(c => c.PrincipleAssessment == overallAssessment.Value);
            }

            query = query.OrderBy(c => c.Principle.Id);

            return query.ToList();
        }

        public int GetGCPrincipleSummaryCount(Guid companyId, GCAssessment? overallAssessment)
        {
            var col = _database.GetCollection<GCPrincipleSummary>("GCPrincipleSummary");

            var query = col.AsQueryable().Where(c => c.CompanyId == companyId);

            if (overallAssessment.HasValue)
            {
                query = query.Where(c => c.PrincipleAssessment == overallAssessment.Value);
            }

            return query.Count();
        }

        public void SaveGCPrincipleSummary(List<GCPrincipleSummary> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCPrincipleSummary>(_connectionString);

            collection.Database.DropCollection(typeof(GCPrincipleSummary).Name);

            collection.InsertMany(documents);

            collection.Indexes.CreateOne(Builders<GCPrincipleSummary>.IndexKeys.Ascending(x => x.CompanyId));
        }

        #endregion

        #region GCPrincipleSummaryExtended

        public List<GCPrincipleSummaryExtended> GetGCPrincipleSummaryExtendedList(Guid companyId, GCAssessment? overallAssessment)
        {
            var id = HasGcResearch(companyId);

            var query = MongoFactory.GetCollectionFromDatabase<GCPrincipleSummaryExtended>(_connectionString)
                .Find(c => c.CompanyId == id)
                .ToEnumerable()
                .AsQueryable();

            if (overallAssessment.HasValue)
            {
                query = query.Where(c => c.PrincipleAssessment == overallAssessment.Value);
            }

            query = query.OrderBy(c => c.Principle.Id);

            return query.ToList();
        }

        public void SaveGCPrincipleSummaryExtended(List<GCPrincipleSummaryExtended> documents)
        {
            var collection = MongoFactory.GetCollectionFromDatabase<GCPrincipleSummaryExtended>(_connectionString);

            collection.Database.DropCollection(typeof(GCPrincipleSummaryExtended).Name);

            collection.InsertMany(documents);

            collection.Indexes.CreateOne(Builders<GCPrincipleSummaryExtended>.IndexKeys.Ascending(x => x.CompanyId));
        }

        #endregion

        public static int ParseCapitalIQ(string a)
        {
            int parse;
            var input = new string(a.Where(Char.IsDigit).ToArray());

            if (int.TryParse(input, out parse) && a.ToLowerInvariant().StartsWith("iqt") || a.ToLowerInvariant().StartsWith("-"))
                parse *= -1;

            return parse;
        }
    }
}
